/*
 * Copyright (c) 2010-2020 Belledonne Communications SARL.
 *
 * This file is part of linphone-android
 * (see https://www.linphone.org).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.newlink.building.core

import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.PixelFormat
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.telephony.TelephonyManager
import android.util.Base64
import android.util.Pair
import android.view.*
import androidx.emoji.bundled.BundledEmojiCompatConfig
import androidx.emoji.text.EmojiCompat
import androidx.lifecycle.*
import com.newlink.building.library_rino.ConnectionController
import com.newlink.building.library_rino.utils.RinoAudioUtils
import com.newlink.building.common_base.constant.Base_Constant
import com.newlink.building.common_base.constant.NL_Key
import com.newlink.building.common_base.event.CallCreateActionBean
import com.newlink.building.common_base.event.CallLogBean
import com.newlink.building.common_base.event.MessageEvent
import com.newlink.building.common_base.event.Base_SipStateEvent
import com.newlink.building.common_base.event.TYPE_CALL_SEND_CREATE_CHANNEL_EQUEST_INCOMING
import com.newlink.building.common_base.event.TYPE_CALL_SEND_CREATE_CHANNEL_REQUEST_INCOMING_EARLYMEDIA
import com.newlink.building.common_base.event.TYPE_UPLOAD_CALL_LOG
import com.newlink.building.common_base.utils.NLog
import com.newlink.building.common_base.utils.Base_Preference
import com.newlink.building.common_base.utils.RegexUtils
import com.newlink.building.NL_App.Companion.corePreferences
import com.newlink.building.R
import com.newlink.building.activities.Module_Phone_MonitorActivityNL
import com.newlink.building.activities.call.Module_Phone_CallActivity
import com.newlink.building.activities.call.Module_Phone_IncomingCallActivity
import com.newlink.building.activities.call.Module_Phone_OutgoingCallActivity
import com.newlink.building.compatibility.Module_Phone_Compatibility
import com.newlink.building.compatibility.ContactsManager
import com.newlink.building.compatibility.Module_Phone_PhoneStateInterface
import com.newlink.building.compatibility.getContactForPhoneNumberOrAddress
import com.newlink.building.notifications.NotificationsManager
import com.newlink.building.utils.*
import com.newlink.building.utils.Module_Phone_Event
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.mediastream.Version
import java.io.File
import java.math.BigInteger
import java.net.Inet4Address
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketException
import java.nio.charset.StandardCharsets
import java.security.KeyStore
import java.security.MessageDigest
import java.text.Collator
import java.text.SimpleDateFormat
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import kotlin.math.abs

class Module_Phone_CoreContext(
    val context: Context, coreConfig: Config
) : LifecycleOwner, ViewModelStoreOwner {

    override val lifecycle = LifecycleRegistry(this)

    private val _viewModelStore = ViewModelStore()


//    private val contactLoader = ContactLoader()

    private val collator: Collator = Collator.getInstance()
    private var agoraId: Int by Base_Preference(Base_Constant.AGORA_ID_KEY, 0)

    var stopped = false
    val core: Core
    val handler: Handler = Handler(Looper.getMainLooper())

    var screenWidth: Float = 0f
    var screenHeight: Float = 0f

    val sdkVersion: String by lazy {
        val sdkVersion = context.getString(R.string.linphone_sdk_version)
        val sdkBranch = context.getString(R.string.linphone_sdk_branch)
        val sdkBuildType = BuildConfig.BUILD_TYPE
        "$sdkVersion ($sdkBranch, $sdkBuildType)"
    }

    val contactsManager: ContactsManager by lazy {
        ContactsManager(context)
    }

    val notificationsManager: NotificationsManager by lazy {
        NotificationsManager(context)
    }

    //记录上一次通话挂断的时间戳
    val callEndTimeMillis: MutableLiveData<Long> by lazy {
        MutableLiveData<Long>()
    }

    val callErrorMessageResourceId: MutableLiveData<Module_Phone_Event<String>> by lazy {
        MutableLiveData<Module_Phone_Event<String>>()
    }

//    lateinit var localFriends: LiveData<List<ContactEntity>>

    private val loggingService = Factory.instance().loggingService
    private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    private var overlayX = 0f
    private var overlayY = 0f
    private var callOverlay: View? = null
    private var previousCallState = Call.State.Idle
    private lateinit var phoneStateListener: Module_Phone_PhoneStateInterface
    var inCallActivity: Boolean = false
    var inMonitorCallActivity: Boolean = false
    var inInComingCallOrOutgoingCallActivity: Boolean = false
    val KEY_HEADER_REMOTE_IP: String = "remote-ip"

    private val listener: CoreListenerStub = object : CoreListenerStub() {

        override fun onCallStatsUpdated(core: Core, call: Call, callStats: CallStats) {
            super.onCallStatsUpdated(core, call, callStats)
            if (callStats.type == StreamType.Video) {
                Log.e("[Context] receivedVideoDefinition: ${call.currentParams.receivedVideoDefinition?.name}")
            }
        }

        override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
            Log.i("[Context] Global state changed [$state]")
            if (state == GlobalState.On) {
                fetchContacts()
            }
        }

        override fun onAccountRegistrationStateChanged(
            core: Core,
            account: Account,
            state: RegistrationState?,
            message: String
        ) {
            Log.i("[Context] Account [${account.params.identityAddress?.asStringUriOnly()}] registration state changed [$state]")

//            if(state == RegistrationState.Progress && account == core.defaultAccount){//有默认账号,正在注册中修改Call-Id
//                val customHeader = corePreferences.customCallId
//                if (!customHeader.isNullOrEmpty() && core.defaultAccount != null) {
//                    core.defaultAccount?.setCustomHeader("Call-ID", customHeader)
//                    Log.i("[Context] RegistrationState Progress setCustomHeader [$customHeader]")
//                    f.refreshRegisters()
//                }
//            }
//
            when (state) {
                RegistrationState.Ok -> {
                    Log.i("[Context] RegistrationState Ok   Call-ID:[${account.getCustomHeader("Call-ID")}]")
                    if (account == core.defaultAccount) {
                        notificationsManager.stopForegroundNotificationIfPossible()
                    }
                }

                RegistrationState.Failed -> {
//                    val isKcpConnected = KcpTaskHelper.instance.isKcpConnected()
//                    Log.i("[Context] RegistrationState Failed isKcpConnected:$isKcpConnected")
//                    //Sip注册失败的时候,如果kcp已经连接需要重新发起一次注册
//                    if(isKcpConnected) {
//                        for (account in core.accountList) {
//                            account.refreshRegister()
//                        }
//                    }
                }

                RegistrationState.Progress -> {
                    Log.i("[Context] RegistrationState Progress ")
                }

                RegistrationState.None -> {
                    Log.i("[Context] RegistrationState None ")
                }

                RegistrationState.Cleared -> {
                    Log.i("[Context] RegistrationState Cleared ")
                }

                else -> {}
            }
            EventBus.getDefault().post(Base_SipStateEvent(state?.toInt() ?: 0))
        }

        override fun onLastCallEnded(core: Core) {
            Log.i("[Context] Last call ended")
            removeCallOverlay()
            if (!core.isMicEnabled) {
                Log.w("[Context] Mic was muted in Core, enabling it back for next call")
                core.isMicEnabled = true
            }
        }

        override fun onCallStateChanged(
            core: Core,
            call: Call,
            state: Call.State,
            message: String
        ) {
            Log.i("[Context] <fvl> Call state changed [$state]")
            if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) {

                //T82086 室内机视频方向为RecvOnly,183默认协商SendOnly,所以预览前要控制下视频方向
//                if (state == Call.State.IncomingReceived) {
//                    val callParams = core.createCallParams(call)
//                    callParams?.videoDirection = MediaDirection.RecvOnly
//                    call.acceptEarlyMediaWithParams(callParams)
//                }

                //设备端Header携带的是否开启预览
                val supportEarlyMedia = call.remoteParams?.getCustomHeader("P-Early-Media")
                Log.i("[Context] <fvl> supportEarlyMedia:[$supportEarlyMedia]")
                if (declineCallDueToGsmActiveCall()) {
                    call.decline(Reason.Busy)
                    return
                }
                // Starting SDK 24 (Android 7.0) we rely on the fullscreen intent of the call incoming notification
//                if (Version.sdkStrictlyBelow(Version.API24_NOUGAT_70)) {
//                    onIncomingReceived()
//                }

                onIncomingReceived(call)

                if (corePreferences.autoAnswerEnabled) {
                    val autoAnswerDelay = corePreferences.autoAnswerDelay
                    if (autoAnswerDelay == 0) {
                        Log.w("[Context] Auto answering call immediately")
                        answerCall(call)
                    } else {
                        Log.i("[Context] Scheduling auto answering in $autoAnswerDelay milliseconds")
                        handler.postDelayed(
                            {
                                Log.w("[Context] Auto answering call")
                                answerCall(call)
                            },
                            autoAnswerDelay.toLong()
                        )
                    }
                }
//                if(core.callsNb == 1) {
//                    val sipMsgEvent = MessageEvent(
//                        TYPE_CALL_SEND_CREATE_CHANNEL_EQUEST_INCOMING ,
//                        CallCreateActionBean(channelName = ConnectionController.getInstance(context).getRemoteChannelName(), aid = agoraId)
//                    )
//                    EventBus.getDefault().post(sipMsgEvent)
//                }
            } else if (state == Call.State.OutgoingInit) {
                //去电时需要清除ConnectionController的缓存
                ConnectionController.getInstance(context).clearCacheAtBeginning()
            } else if (state == Call.State.OutgoingProgress) {
                if (core.callsNb == 1 && corePreferences.routeAudioToBluetoothIfAvailable) {
                    Module_Phone_AudioRouteUtils.routeAudioToBluetooth(call)
                }
                if (call.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                    onMonitorCallStarted()
                } else {
                    onOutgoingStarted()
                }
            } else if (state == Call.State.Connected) {
                stopRingtone()
                if (corePreferences.automaticallyStartCallRecording) {
                    Log.i("[Context] We were asked to start the call recording automatically")
                    call.startRecording()
                }
                if (core.callsNb > 1) {
                    //由于监控支持二路通话,新路来电接听需要挂断其他来电
                    terminateOtherCall(call)
                }
                if (!inMonitorCallActivity) {
                    onCallStarted()
                }
            } else if (state == Call.State.StreamsRunning) {
                val usedAudioPayloadType = call.currentParams.usedAudioPayloadType
                val usedVideoPayloadType = call.currentParams.usedVideoPayloadType
                call.currentParams.receivedVideoDefinition
                ConnectionController.getInstance(context.applicationContext)
                    .convertSip2AgoraAudioCodec(
                        usedAudioPayloadType?.mimeType,
                        usedAudioPayloadType?.clockRate
                    )
                Log.i("[Context]  usedAudioPayloadType mimeType:${usedAudioPayloadType?.mimeType} clockRate:${usedAudioPayloadType?.clockRate ?: 0} Hz} ")
                Log.i("[Context]  usedVideoPayloadType mimeType:${usedVideoPayloadType?.mimeType} clockRate:${usedVideoPayloadType?.clockRate ?: 0} Hz}")
                // Do not automatically route audio to bluetooth after first call
                if (core.callsNb == 1) {
                    // Only try to route bluetooth / headphone / headset when the call is in StreamsRunning for the first time
                    if (previousCallState == Call.State.Connected) {
                        Log.i("[Context] First call going into StreamsRunning state for the first time, trying to route audio to headset or bluetooth if available")
                        if (Module_Phone_AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
                            Module_Phone_AudioRouteUtils.routeAudioToHeadset(call)
                        } else if (corePreferences.routeAudioToBluetoothIfAvailable && Module_Phone_AudioRouteUtils.isBluetoothAudioRouteAvailable()) {
                            Module_Phone_AudioRouteUtils.routeAudioToBluetooth(call)
                        }
                    }
                }

//                if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.isVideoEnabled) {
//                    // Do not turn speaker on when video is enabled if headset or bluetooth is used
//                    if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(
//                            call
//                        )
//                    ) {
//                        Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
//                        if(call.params.getCustomHeader(FvlKey.CALL_INFO) != FvlKey.EXTRA_MONITOR){//视频通话切到扬声器
//                            AudioRouteUtils.routeAudioToSpeaker(call)
//                        }
//                    }
//                }
            } else if (state == Call.State.End || state == Call.State.Error || state == Call.State.Released) {
                Log.i("[Context] <fvl> 2222222222222222")

                callEndTimeMillis.value = System.currentTimeMillis()

                if (state == Call.State.End) {
                    Log.i("[Context] <fvl> 66666 ${core.callsNb}")
                    //监控中来电清一下player,避免下一路为预览时加入房间失败的情况
                    if (core.callsNb == 1 && call.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                        ConnectionController.getInstance(context).releasePlayer()
                    }
                    //挂断时无通话存在时要清下标记,保证下次可以正常加入通道
                    if (core.callsNb == 0) {
                        ConnectionController.getInstance(context).cleanJoinChanneJob()
                    }
                }

                if (state == Call.State.Released || state == Call.State.Error) {
                    NLog.e("[CoreContext] state >> $state  ; dir  >> ${call.dir}")
                    //收到Released时间后再清理ChannelName
                    if (core.callsNb == 0) {
                        ConnectionController.getInstance(context).leaveChannel()
                    }
                    // 主叫上传通话记录
                    if (call.dir == Call.Dir.Outgoing) {

                        val callerNumber =
                            core.defaultAccount?.params?.identityAddress?.username.orEmpty()
                        val answerNumber = call.remoteAddress.username.orEmpty()
                        //挂断原因 0.正常结束通话 1.用户按拒接键 2.开启了DND 3.在黑名单中 4.port busy 5.媒体协商出错
                        var hangupCause = when (call.errorInfo.reason) {
                            Reason.Declined -> 1
                            Reason.DoNotDisturb -> 2
                            Reason.Forbidden -> 3
                            Reason.Busy -> 4
                            Reason.NoMatch -> 5
                            else -> 0

                        }
                        if (call.duration == 0) {
                            hangupCause = 1
                        }
                        NLog.e("主叫挂断 >> $callerNumber >> $answerNumber")

                        val callLogBean = CallLogBean(
                            data = CallLogBean.Data(
                                callerNumber = callerNumber,
                                answerNumber = answerNumber,
                                startStamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
                                    Date(
                                        call.callLog.startDate * 1000
                                    )
                                ),
                                duration = call.duration.toString(),
                                hangupCause = hangupCause.toString(),
                                callType = if (call.params?.isVideoEnabled == true) 1 else 0
                            )
                        )
                        NLog.e("callLogBean : is $callLogBean")
                        EventBus.getDefault().post(MessageEvent(TYPE_UPLOAD_CALL_LOG, callLogBean))
                    }
                    //1.监控挂断不需要停止振铃 2.callNb=0停止振铃,防止一路挂断后二路来电不振铃情况
                    if (call.params?.getCustomHeader(NL_Key.CALL_INFO) != NL_Key.EXTRA_MONITOR && core.callsNb == 0) {
                        stopRingtone()
                    }
                }

                if (state == Call.State.Error) {
                    Log.w("[Context] Call error reason is ${call.errorInfo.protocolCode} / ${call.errorInfo.reason} / ${call.errorInfo.phrase}")
                    val message = when (call.errorInfo.reason) {
                        Reason.Busy -> context.getString(R.string.call_error_user_busy)
                        Reason.Forbidden -> context.getString(R.string.call_error_remote_forbidden)
//                        Reason.IOError -> context.getString(R.string.call_error_io_error)
                        Reason.NotAcceptable -> context.getString(R.string.call_error_incompatible_media_params)
                        Reason.NotFound -> context.getString(R.string.call_error_user_not_found)
//                        Reason.ServerTimeout -> context.getString(R.string.call_error_server_timeout)
                        Reason.TemporarilyUnavailable -> context.getString(R.string.call_error_remote_forbidden)
                        else -> context.getString(R.string.call_error_generic)
                            .format("${call.errorInfo.phrase}")
                    }
                    callErrorMessageResourceId.value = Module_Phone_Event(message)
                } else if (state == Call.State.End &&
                    call.errorInfo.reason == Reason.Declined
                ) {
                    Log.i("[Context] Call has been declined $previousCallState")
                    if (previousCallState == Call.State.OutgoingProgress || previousCallState == Call.State.OutgoingRinging) {
                        val message = context.getString(R.string.call_error_declined)
                        callErrorMessageResourceId.value = Module_Phone_Event(message)
                    }
                }
            }
            previousCallState = state
        }

        override fun onMessageReceived(core: Core, chatRoom: ChatRoom, message: ChatMessage) {
            if (core.maxSizeForAutoDownloadIncomingFiles != -1) {
                var hasFile = false
                for (content in message.contents) {
                    if (content.isFile) {
                        hasFile = true
                        break
                    }
                }
//                if (hasFile) {
//                    exportFilesInMessageToMediaStore(message)
//                }
            }
        }
    }

    private val loggingServiceListener = object : LoggingServiceListenerStub() {
        override fun onLogMessageWritten(
            logService: LoggingService,
            domain: String,
            level: LogLevel,
            message: String
        ) {
            if (corePreferences.logcatLogsOutput) {
                when (level) {
                    LogLevel.Error -> android.util.Log.e(domain, message)
                    LogLevel.Warning -> android.util.Log.w(domain, message)
                    LogLevel.Message -> android.util.Log.i(domain, message)
                    LogLevel.Fatal -> android.util.Log.wtf(domain, message)
                    else -> android.util.Log.d(domain, message)
                }
            }
//            FirebaseCrashlytics.getInstance().log("[$domain] [${level.name}] $message")
        }
    }

    //    val mRingtone: Ringtone by lazy {
//        RingtoneManager.getRingtone(context, ringtoneUri)
//    }
    private var mediaPlayer: MediaPlayer? = null

    init {
//        if (context.resources.getBoolean(R.bool.crashlytics_enabled)) {
//            loggingService.addListener(loggingServiceListener)
//            Log.i("[Context] Crashlytics enabled, register logging service listener")
//        }
//
//        Log.i("=========================================")
//        Log.i("==== Linphone-android information dump ====")
//        Log.i("VERSION=${BuildConfigh.VERSION_NAME} / ${BuildConfig.VERSION_CODE}")
//        Log.i("PACKAGE=${BuildConfig.APPLICATION_ID}")
//        Log.i("BUILD TYPE=${BuildConfig.BUILD_TYPE}")
//        Log.i("=========================================")
        core = Factory.instance().createCoreWithConfig(coreConfig, context)
        core.isIpv6Enabled = false
        //从kcp库中获取proxy地址和端口号
        core.setStrictProxyEnabled(true)
        core.setStrictProxyAddress("127.0.0.1")
        core.setStrictProxyPort(Base_Constant.NETWORK_LISTEN_PORT)
        core.isNativeRingingEnabled = false
        initAudioCodec()
        initVideoCodec()

        stopped = false
        lifecycle.currentState = Lifecycle.State.INITIALIZED
        Log.i("[Context] Ready")

        inCallActivity = false
        inMonitorCallActivity = false
//        initLocalFriends()
    }

    private val supportAudioCodec = mutableListOf("opus", "PCMU", "G722")
    fun initAudioCodec() {
        for ((index, payload) in core.audioPayloadTypes.withIndex()) {

            when (payload.mimeType) {
                //TODO 暂时还原release版本 仅支持g711a
                "PCMA" -> payload.enable(true)
                //TODO 开启默认支持的编码,目前顺序为 PCMA/PCMU/G722/OPUS
//                "PCMA", "PCMU", "G722", "opus" -> payload.enable(true)
                else -> payload.enable(false)
            }

            Log.i("[Context] Audio PayloadType mimeType:${payload.mimeType} index:$index recvFmtp:${payload.recvFmtp} clockRate:${payload.clockRate} kHz")
        }
    }

    fun initVideoCodec() {
        for ((index, payload) in core.videoPayloadTypes.withIndex()) {
            if (payload.mimeType == "H264") {
                //level默认为31 既profile-level-id=42801F
                payload.recvFmtp = "profile-level-id=42801F"
            }
            Log.i("[Context] Video PayloadType mimeType:${payload.mimeType} index:$index recvFmtp:${payload.recvFmtp}")
        }
    }

    fun start(isPush: Boolean = false) {
        Log.i("[Context] Starting")

        core.addListener(listener)

        // CoreContext listener must be added first!
        if (Version.sdkAboveOrEqual(Version.API26_O_80) && corePreferences.useTelecomManager) {
            if (Module_Phone_Compatibility.hasTelecomManagerPermissions(context)) {
                Log.i("[Context] Creating Telecom Helper, disabling audio focus requests in AudioHelper")
                core.config.setBool("audio", "android_disable_audio_focus_requests", true)
                val telecomHelper = Module_Phone_TelecomHelper.required(context)
                Log.i("[Context] Telecom Helper created, account is ${if (telecomHelper.isAccountEnabled()) "enabled" else "disabled"}")
            } else {
                Log.w("[Context] Can't create Telecom Helper, permissions have been revoked")
                corePreferences.useTelecomManager = false
            }
        }

        if (isPush) {
            Log.i("[Context] Push received, assume in background")
            core.enterBackground()
        }

        configureCore()
//        val customHeader = corePreferences.customCallId
//        if (!customHeader.isNullOrEmpty() && core.defaultAccount != null) {
//            core.defaultAccount?.setCustomHeader("Call-ID", customHeader)
//            Log.e("setCustomHeader  customHeader:$customHeader")
//        }
//        lifecycle.currentState = Lifecycle.State.CREATED
        core.start()
        lifecycle.currentState = Lifecycle.State.STARTED

        initPhoneStateListener()

        notificationsManager.onCoreReady()

        EmojiCompat.init(BundledEmojiCompatConfig(context))
        collator.strength = Collator.NO_DECOMPOSITION

        if (corePreferences.vfsEnabled) {
            Module_Phone_FileUtils.clearExistingPlainFiles()
        }

        if (corePreferences.keepServiceAlive) {
            Log.i("[Context] Background mode setting is enabled, starting Service")
            notificationsManager.startForeground()
        }

        lifecycle.currentState = Lifecycle.State.RESUMED
        Log.i("[Context] Started")
    }


    fun stop() {
        Log.i("[Context] Stopping")
        coroutineScope.cancel()

        if (::phoneStateListener.isInitialized) {
            phoneStateListener.destroy()
        }
        notificationsManager.destroy()
        contactsManager.destroy()
        if (Module_Phone_TelecomHelper.exists()) {
            Log.i("[Context] Destroying telecom helper")
            Module_Phone_TelecomHelper.get().destroy()
            Module_Phone_TelecomHelper.destroy()
        }

        core.stop()
        core.removeListener(listener)
        stopped = true
        loggingService.removeListener(loggingServiceListener)

        lifecycle.currentState = Lifecycle.State.DESTROYED
    }

    private fun configureCore() {
        Log.i("[Context] Configuring Core")

        core.staticPicture = corePreferences.staticPicturePath

        // Migration code
        if (core.config.getBool("app", "incoming_call_vibration", true)) {
            core.isVibrationOnIncomingCallEnabled = true
            core.config.setBool("app", "incoming_call_vibration", false)
        }

        initUserCertificates()

        computeUserAgent()

        for (account in core.accountList) {
            if (account.params.identityAddress?.domain == corePreferences.defaultDomain) {
                // Ensure conference URI is set on sip.linphone.org proxy configs
                if (account.params.conferenceFactoryUri == null) {
                    val params = account.params.clone()
                    val uri = corePreferences.conferenceServerUri
                    Log.i("[Context] Setting conference factory on proxy config ${params.identityAddress?.asString()} to default value: $uri")
                    params.conferenceFactoryUri = uri
                    account.params = params
                }

                // Ensure we allow CPIM messages in basic chat rooms
                val newParams = account.params.clone()
                newParams.isCpimInBasicChatRoomEnabled = true
                account.params = newParams
                Log.i("[Context] CPIM allowed in basic chat rooms for account ${newParams.identityAddress?.asStringUriOnly()}")
            }
        }

        Log.i("[Context] Core configured")
    }

    private fun computeUserAgent() {
        val deviceName: String = corePreferences.deviceName
        val appName: String = context.resources.getString(R.string.user_agent_app_name)
        val androidVersion = "4.6.12"
        val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK"
        val sdkVersion = context.getString(R.string.linphone_sdk_version)
        val sdkBranch = context.getString(R.string.linphone_sdk_branch)
        val sdkUserAgent = "$sdkVersion ($sdkBranch)"
        core.setUserAgent(userAgent, sdkUserAgent)
    }

    private fun initUserCertificates() {
        val userCertsPath = corePreferences.userCertificatesPath
        val f = File(userCertsPath)
        if (!f.exists()) {
            if (!f.mkdir()) {
                Log.e("[Context] $userCertsPath can't be created.")
            }
        }
        core.userCertificatesPath = userCertsPath
    }

    fun fetchContacts() {
        if (Module_Phone_PermissionHelper.required(context).hasReadContactsPermission()) {
            Log.i("[Context] Init contacts loader")
//            val manager = LoaderManager.getInstance(this@CoreContext)
//            manager.restartLoader(0, null, contactLoader)
        }
    }

    fun newAccountConfigured(isLinphoneAccount: Boolean) {
        Log.i("[Context] A new ${if (isLinphoneAccount) Module_Phone_AppUtils.getString(R.string.app_name) else "third-party"} account has been configured")

        if (isLinphoneAccount) {
            core.config.setString("sip", "rls_uri", corePreferences.defaultRlsUri)
            val rlsAddress = core.interpretUrl(corePreferences.defaultRlsUri)
            if (rlsAddress != null) {
                for (friendList in core.friendsLists) {
                    friendList.rlsAddress = rlsAddress
                }
            }

            if (core.limeX3DhAvailable()) {
                core.limeX3DhServerUrl = corePreferences.defaultLimeServerUrl
            }
        } else {
            Log.i("[Context] Background mode with foreground service automatically enabled")
            corePreferences.keepServiceAlive = true
            notificationsManager.startForeground()
        }

        contactsManager.updateLocalContacts()
    }

    /* Call related functions */

    fun initPhoneStateListener() {
        if (Module_Phone_PermissionHelper.required(context).hasReadPhoneStatePermission()) {
            try {
                phoneStateListener =
                    Module_Phone_Compatibility.createPhoneListener(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
            } catch (exception: SecurityException) {
                val hasReadPhoneStatePermission =
                    Module_Phone_PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()
                Log.e("[Context] Failed to create phone state listener: $exception, READ_PHONE_STATE permission status is $hasReadPhoneStatePermission")
            }
        } else {
            Log.w("[Context] Can't create phone state listener, READ_PHONE_STATE permission isn't granted")
        }
    }

    fun declineCallDueToGsmActiveCall(): Boolean {
        if (!corePreferences.useTelecomManager) { // Can't use the following call with Telecom Manager API as it will "fake" GSM calls
            var gsmCallActive = false
            if (::phoneStateListener.isInitialized) {
                gsmCallActive = phoneStateListener.isInCall()
            }

            if (gsmCallActive) {
                Log.w("[Context] Refusing the call with reason busy because a GSM call is active")
                return true
            }
        } else {
            if (Module_Phone_TelecomHelper.exists()) {
                if (!Module_Phone_TelecomHelper.get().isIncomingCallPermitted() ||
                    Module_Phone_TelecomHelper.get().isInManagedCall()
                ) {
                    Log.w("[Context] Refusing the call with reason busy because Telecom Manager will reject the call")
                    return true
                }
            } else {
                Log.e("[Context] Telecom Manager singleton wasn't created!")
            }
        }
        return false
    }

    fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) {
        val params = core.createCallParams(call)

        if (accept) {
            params?.isVideoEnabled = true
            core.isVideoCaptureEnabled = true
            core.isVideoDisplayEnabled = true
        } else {
            params?.isVideoEnabled = false
        }

        call.acceptUpdate(params)
    }

    fun answerCall(call: Call) {
        if (earlyMediaVideoEnabled(call)) {
            ConnectionController.getInstance(context).releasePlayer(false)
        }
        answerCall(call, call.currentParams.isVideoEnabled)
    }

    fun earlyMediaVideoEnabled(call: Call): Boolean {
        return corePreferences.globalSupportEarlyMedia && corePreferences.acceptEarlyMedia &&
                call.state == Call.State.IncomingEarlyMedia && call.currentParams.isVideoEnabled
    }

    fun answerCall(call: Call, isVideo: Boolean) {
        if (!inCallActivity) {
            val policy = core.videoActivationPolicy
            policy.automaticallyAccept = true
            policy.automaticallyInitiate = true
            core.videoActivationPolicy = policy

            Log.i("[Context] Answering call $call")
            val params = core.createCallParams(call)
            params?.recordFile = Module_Phone_LinphoneUtils.getRecordingFilePathForAddress(call.remoteAddress)
            if (Module_Phone_LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) {
                Log.w("[Context] Enabling low bandwidth mode!")
                params?.isLowBandwidthEnabled = true
            }
            params?.isVideoEnabled = isVideo
            call.acceptWithParams(params)
        } else {
            Log.e("[Context] On the line, abort answer call")
            terminateOtherCall()
//            callErrorMessageResourceId.value =
//                Event(context.getString(R.string.call_error_on_the_line))
        }
    }

    fun declineCall(call: Call) {
        val voiceMailUri = corePreferences.voiceMailUri
        if (voiceMailUri != null && corePreferences.redirectDeclinedCallToVoiceMail) {
            val voiceMailAddress = core.interpretUrl(voiceMailUri)
            if (voiceMailAddress != null) {
                Log.i("[Context] Redirecting call $call to voice mail URI: $voiceMailUri")
                call.redirectTo(voiceMailAddress)
            }
        } else {
            Log.i("[Context] Declining call $call")
            call.decline(Reason.Declined)
        }
    }

    fun terminateCall(call: Call) {
        Log.i("[Context] Terminating call $call")
        call.terminate()
    }

    fun terminateOtherCall() {
        Log.i("[Context] Terminating Other call")
        core.currentCall?.let { terminateOtherCall(it) }
    }

    fun terminateOtherCall(call: Call) {
        Log.i("[Context] Terminating Other call")
        val calls = core.calls
        for (tempCall in calls) {
            if (tempCall != call) {
                //显示忙线
                tempCall.decline(Reason.Busy)
//                tempCall.terminate()
            }
        }
    }

    fun transferCallTo(addressToCall: String) {
        val currentCall = core.currentCall ?: core.calls.firstOrNull()
        if (currentCall == null) {
            Log.e("[Context] Couldn't find a call to transfer")
        } else {
            val address = core.interpretUrl(addressToCall)
            if (address != null) {
                Log.i("[Context] Transferring current call to $addressToCall")
                currentCall.transferTo(address)
            }
        }
    }

    fun startCall(to: String) {
        if (!inCallActivity && !inInComingCallOrOutgoingCallActivity) {
            if (core.defaultAccount?.state != RegistrationState.Ok &&
                !to.contains('.') &&
                !to.contains('*')
            ) {
                Log.e("[Context] unregister, The current line is unavailable")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_line_unavailable))
                return
            }

            var stringAddress = to
            stringAddress = stringAddress.replace("*", ".", true)
            if (stringAddress == core.defaultAccount?.params?.identityAddress?.username ||
                stringAddress == getIpAddressString()
            ) {
                Log.e("[Context] Do not call yourself")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_user_busy))
                return
            }

            if (stringAddress.contains(".")) {
                if (!RegexUtils.checkIpAddress(stringAddress)) {
                    callErrorMessageResourceId.value =
                        Module_Phone_Event(context.getString(R.string.contact_error_ip_address_incorrect))
                    return
                }
                stringAddress = "sip:$stringAddress"
            }

            if (android.util.Patterns.PHONE.matcher(to).matches()) {
                val contact = contactsManager.findContactByPhoneNumber(to)
                val alias = contact?.getContactForPhoneNumberOrAddress(to)
                if (alias != null) {
                    Log.i("[Context] Found matching alias $alias for phone number $to, using it")
                    stringAddress = alias
                }
            }

            val address: Address? = core.interpretUrl(stringAddress)
            if (address == null) {
                Log.e("[Context] Failed to parse $stringAddress, abort outgoing call")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_network_unreachable))
                return
            }

            startCall(address)
        } else {
            Log.e("[Context] On the line, abort start call")
            terminateOtherCall()
            callErrorMessageResourceId.value =
                Module_Phone_Event(context.getString(R.string.call_error_on_the_line))
        }
    }

    fun startCall(address: Address, forceZRTP: Boolean = false, localAddress: Address? = null) {
        if (!inCallActivity && !inInComingCallOrOutgoingCallActivity) {
            if (core.defaultAccount?.state != RegistrationState.Ok &&
                !address.asString().contains('.') &&
                !address.asString().contains('*')
            ) {
                Log.e("[Context] unregister, The current line is unavailable")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_line_unavailable))
                return
            }

            if (address.username == core.defaultAccount?.params?.identityAddress?.username ||
                address.domain == getIpAddressString()
            ) {
                Log.e("[Context] Do not call yourself")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_user_busy))
                return
            }

            if (!core.isNetworkReachable) {
                Log.e("[Context] Network unreachable, abort outgoing call")
                callErrorMessageResourceId.value =
                    Module_Phone_Event(context.getString(R.string.call_error_network_unreachable))
                return
            }

//            if (core.defaultAccount?.params?.isRegisterEnabled != true) {
//                Log.e("[Context] unregister, The current line is unavailable")
//                callErrorMessageResourceId.value =
//                    Event(context.getString(R.string.call_error_line_unavailable))
//                return
//            }

            val params = core.createCallParams(null)
            if (params == null) {
                val call = core.inviteAddress(address)
                Log.w("[Context] Starting call $call without params")
                return
            }

            if (forceZRTP) {
                params.mediaEncryption = MediaEncryption.ZRTP
            }
            if (Module_Phone_LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) {
                Log.w("[Context] Enabling low bandwidth mode!")
                params.isLowBandwidthEnabled = true
            }
            params.recordFile = Module_Phone_LinphoneUtils.getRecordingFilePathForAddress(address)

            if (localAddress != null) {
                params.proxyConfig = core.proxyConfigList.find { proxyConfig ->
                    proxyConfig.identityAddress?.weakEqual(localAddress) ?: false
                }
                if (params.proxyConfig != null) {
                    Log.i("[Context] Using proxy config matching address ${localAddress.asStringUriOnly()} as From")
                }
            }

            if (corePreferences.sendEarlyMedia) {
                params.isEarlyMediaSendingEnabled = true
            }

            params.isVideoEnabled = true

            if (address.asString().contains('@')) {
                params.addCustomHeader(KEY_HEADER_REMOTE_IP, "")
            } else {
                params.addCustomHeader(KEY_HEADER_REMOTE_IP, getIpAddressString())
            }
            val call = core.inviteAddressWithParams(address, params)
            Log.i("[Context] Starting call $call")
        } else {
            Log.e("[Context] On the line, abort start call")
            terminateOtherCall()
            callErrorMessageResourceId.value =
                Module_Phone_Event(context.getString(R.string.call_error_on_the_line))
        }
    }

    fun getIpAddressString(): String? {
        try {
            val enNetI: Enumeration<NetworkInterface> = NetworkInterface
                .getNetworkInterfaces()
            while (enNetI.hasMoreElements()) {
                val netI: NetworkInterface = enNetI.nextElement()
                val enumIpAddr: Enumeration<InetAddress> = netI
                    .inetAddresses
                while (enumIpAddr.hasMoreElements()) {
                    val inetAddress: InetAddress = enumIpAddr.nextElement()
                    if (inetAddress is Inet4Address && !inetAddress.isLoopbackAddress()) {
                        return inetAddress.getHostAddress()
                    }
                }
            }
        } catch (e: SocketException) {
            e.printStackTrace()
        }
        return "0.0.0.0"
    }

    fun switchCamera() {
        val currentDevice = core.videoDevice
        Log.i("[Context] Current camera device is $currentDevice")

        for (camera in core.videoDevicesList) {
            if (camera != currentDevice && camera != "StaticImage: Static picture") {
                Log.i("[Context] New camera device will be $camera")
                core.videoDevice = camera
                break
            }
        }

        val conference = core.conference
        if (conference == null || !conference.isIn) {
            val call = core.currentCall
            if (call == null) {
                Log.w("[Context] Switching camera while not in call")
                return
            }
            call.update(null)
        }
    }

    fun showSwitchCameraButton(): Boolean {
        return core.videoDevicesList.size > 2 // Count StaticImage camera
    }

    fun isVideoCallOrConferenceActive(): Boolean {
        val conference = core.conference
        return if (conference != null && conference.isIn) {
            conference.currentParams.isVideoEnabled
        } else {
            core.currentCall?.currentParams?.isVideoEnabled ?: false
        }
    }

    fun createCallOverlay() {
        if (!corePreferences.showCallOverlay || !corePreferences.systemWideCallOverlay || callOverlay != null) {
            return
        }

        if (overlayY == 0f) overlayY = Module_Phone_AppUtils.pixelsToDp(40f)
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        // WRAP_CONTENT doesn't work well on some launchers...
        val params: WindowManager.LayoutParams = WindowManager.LayoutParams(
            Module_Phone_AppUtils.getDimension(R.dimen.call_overlay_size).toInt(),
            Module_Phone_AppUtils.getDimension(R.dimen.call_overlay_size).toInt(),
            Module_Phone_Compatibility.getOverlayType(),
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )
        params.x = overlayX.toInt()
        params.y = overlayY.toInt()
        params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
        val overlay = LayoutInflater.from(context).inflate(R.layout.call_overlay, null)

        var initX = overlayX
        var initY = overlayY
        overlay.setOnTouchListener { view, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    initX = params.x - event.rawX
                    initY = params.y - event.rawY
                }

                MotionEvent.ACTION_MOVE -> {
                    val x = (event.rawX + initX).toInt()
                    val y = (event.rawY + initY).toInt()

                    params.x = x
                    params.y = y
                    windowManager.updateViewLayout(overlay, params)
                }

                MotionEvent.ACTION_UP -> {
                    if (abs(overlayX - params.x) < Module_Phone_CorePreferences.OVERLAY_CLICK_SENSITIVITY &&
                        abs(overlayY - params.y) < Module_Phone_CorePreferences.OVERLAY_CLICK_SENSITIVITY
                    ) {
                        view.performClick()
                    }
                    overlayX = params.x.toFloat()
                    overlayY = params.y.toFloat()
                }

                else -> return@setOnTouchListener false
            }
            true
        }
        overlay.setOnClickListener {
            onCallOverlayClick()
        }

        try {
            windowManager.addView(overlay, params)
            callOverlay = overlay
        } catch (e: Exception) {
            Log.e("[Context] Failed to add overlay in windowManager: $e")
        }
    }

    /**
     * 当前通话中是否存在监控
     */
    fun exitsMonitorCall(): Boolean {
        var exit = false
        core.calls.forEach {
            if (it.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                exit = true
            }
        }
        return exit
    }

    fun onCallOverlayClick(
        currentCall: Call? = core.currentCall,
        state: Call.State? = core.currentCall?.state
    ) {
        val call = currentCall ?: core.calls.firstOrNull()
        if (call != null) {
            Log.i("[Context] Overlay clicked, go back to call view")
            when (state) {
                Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
                    Log.i("[Context] Overlay clicked, current calls number:${core.callsNb} inCallActivity:$inCallActivity")
                    onIncomingReceived(call)
                }

                Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging, Call.State.OutgoingEarlyMedia -> {
                    if (call.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                        onMonitorCallStarted()
                    } else {
                        onOutgoingStarted()
                    }
                }

                else -> {
                    if (call.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                        onMonitorCallStarted()
                    } else {
                        onCallStarted()
                    }
                }
            }
        } else {
            Log.e("[Context] Couldn't find call, why is the overlay clicked?!")
        }
    }

    fun removeCallOverlay() {
        if (callOverlay != null) {
            val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
            windowManager.removeView(callOverlay)
            callOverlay = null
        }
    }

    /* Coroutine related */

//    fun exportFilesInMessageToMediaStore(message: ChatMessage) {
//        if (message.isEphemeral) {
//            Log.w("[Context] Do not make ephemeral file(s) public")
//            return
//        }
//        if (corePreferences.vfsEnabled) {
//            Log.w("[Context] Do not make received file(s) public when VFS is enabled")
//            return
//        }
//        if (!corePreferences.makePublicMediaFilesDownloaded) {
//            Log.w("[Context] Making received files public setting disabled")
//            return
//        }
//
//        if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get()
//                .hasWriteExternalStoragePermission()
//        ) {
//            for (content in message.contents) {
//                if (content.isFile && content.filePath != null && content.userData == null) {
//                    addContentToMediaStore(content)
//                }
//            }
//        } else {
//            Log.e("[Context] Can't make file public, app doesn't have WRITE_EXTERNAL_STORAGE permission")
//        }
//    }

//    fun addContentToMediaStore(content: Content) {
//        if (corePreferences.vfsEnabled) {
//            Log.w("[Context] Do not make received file(s) public when VFS is enabled")
//            return
//        }
//        if (!corePreferences.makePublicMediaFilesDownloaded) {
//            Log.w("[Context] Making received files public setting disabled")
//            return
//        }
//
//        if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get()
//                .hasWriteExternalStoragePermission()
//        ) {
//            coroutineScope.launch {
//                when (content.type) {
//                    "image" -> {
//                        if (Compatibility.addImageToMediaStore(context, content)) {
//                            Log.i("[Context] Adding image ${content.name} to Media Store terminated")
//                        } else {
//                            Log.e("[Context] Something went wrong while copying file to Media Store...")
//                        }
//                    }
//                    "video" -> {
//                        if (Compatibility.addVideoToMediaStore(context, content)) {
//                            Log.i("[Context] Adding video ${content.name} to Media Store terminated")
//                        } else {
//                            Log.e("[Context] Something went wrong while copying file to Media Store...")
//                        }
//                    }
//                    "audio" -> {
//                        if (Compatibility.addAudioToMediaStore(context, content)) {
//                            Log.i("[Context] Adding audio ${content.name} to Media Store terminated")
//                        } else {
//                            Log.e("[Context] Something went wrong while copying file to Media Store...")
//                        }
//                    }
//                    else -> {
//                        Log.w("[Context] File ${content.name} isn't either an image, an audio file or a video, can't add it to the Media Store")
//                    }
//                }
//            }
//        }
//    }

    fun checkIfForegroundServiceNotificationCanBeRemovedAfterDelay(delayInMs: Long) {
        coroutineScope.launch {
            withContext(Dispatchers.Default) {
                delay(delayInMs)
                withContext(Dispatchers.Main) {
                    if (core.defaultAccount != null && core.defaultAccount?.state == RegistrationState.Ok) {
                        Log.i("[Context] Default account is registered, cancel foreground service notification if possible")
                        notificationsManager.stopForegroundNotificationIfPossible()
                    }
                }
            }
        }
    }

    /* Start call related activities */

    private fun onIncomingReceived(call: Call) {
        Log.e("[Context] <fvl> Starting onIncomingReceived:: $inCallActivity , $inInComingCallOrOutgoingCallActivity, ${core.callsNb}")
        //二路通话有监控时,挂断监控
        if (core.callsNb > 1) {
            val firstCall = core.calls.first()
            if (firstCall.params?.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                firstCall.terminate()
                RinoAudioUtils.setAudioManagerInNormalMode(context)
            }
        }

        //当前在通话界面 或二路IncomingCall存在的话提示用户正忙
        if (inCallActivity || core.callsNb > 1) {
            call.decline(Reason.Busy)
            return
        }

        //来电和去电时暂时不允许第二路来电
        if (!inCallActivity && !inInComingCallOrOutgoingCallActivity) {
            if (corePreferences.preventInterfaceFromShowingUp) {
                Log.w("[Context] We were asked to not show the incoming call screen")
                return
            }

            playRingtone()
            Log.e("[Context] <fvl> Starting IncomingCallActivity")
            val intent = Intent(context, Module_Phone_IncomingCallActivity::class.java)
            // This flag is required to start an Activity from a Service context
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
            context.startActivity(intent)

            if (call.state == Call.State.IncomingReceived) {
                if (!corePreferences.acceptEarlyMedia) {
                    createIncomingRinoChannel()
                }
            } else if (call.state == Call.State.IncomingEarlyMedia) {
                ConnectionController.getInstance(context).clearAgoraUserToken()
                ConnectionController.getInstance(context).cleanJoinChanneJob()
                //视频预览时延迟获取token,防止EventBus未注册成功的情况
                coroutineScope.launch {
                    delay(300)
                    withContext(Dispatchers.Main) {
                        createIncomingEarlyMediaRinoChannel()
                    }
                }
            }

        } else {
            terminateOtherCall()
        }
    }

    private fun playRingtone() {
        core.stopRinging()
        NLog.d("[fvl] >>>>>> Playing ringtone >> native ring = ")
        //铃声在播放不创建新的player
        if (mediaPlayer != null) {
            return
        }

        val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
//        mRingtone.play()
        val ringtoneUri: Uri =
            Uri.parse(context.filesDir.absolutePath + "/share/sounds/linphone/rings/notes_of_the_optimistic.mkv")
//        val ringtoneUri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
        mediaPlayer = MediaPlayer.create(context, ringtoneUri)
        mediaPlayer?.apply {
//            setAudioStreamType(AudioManager.STREAM_RING)
            setAudioAttributes(
                AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build()
            )
            isLooping = true
            setOnPreparedListener {
                Log.d("LinphoneService", "MediaPlayer prepared and starting")
                start()
            }
            setOnCompletionListener {
                Log.d("LinphoneService", "MediaPlayer completed")
                release()
            }
            setOnErrorListener { _, what, extra ->
                Log.e("LinphoneService", "MediaPlayer error: $what, $extra")
                true
            }
        }
    }

    private fun stopRingtone() {
        NLog.d("[fvl]  >>>>>>  stopRingtone")
        mediaPlayer?.let {
            if (it.isPlaying) {
                it.stop()
            }
            it.release()
            mediaPlayer = null
            Log.d("LinphoneService", "[fvl] Ringtone stopped and MediaPlayer released")
        }
    }

    private fun onOutgoingStarted() {
        if (!inCallActivity && !inInComingCallOrOutgoingCallActivity) {
            if (corePreferences.preventInterfaceFromShowingUp) {
                Log.w("[Context] We were asked to not show the outgoing call screen")
                return
            }

            Log.i("[Context] Starting OutgoingCallActivity")
            val intent = Intent(context, Module_Phone_OutgoingCallActivity::class.java)
            // This flag is required to start an Activity from a Service context
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
            context.startActivity(intent)
        } else {
            terminateOtherCall()
        }
    }

    fun onMonitorCallStarted() {
        Log.w("[Context] onMonitorCallStarted...")
        val intent = Intent(context, Module_Phone_MonitorActivityNL::class.java)
        // This flag is required to start an Activity from a Service context
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
        context.startActivity(intent)
    }

    fun onCallStarted() {
        Log.w("[Context] onCallStarted $inCallActivity")
        if (!inCallActivity) {
            if (corePreferences.preventInterfaceFromShowingUp) {
                Log.w("[Context] We were asked to not show the call screen")
                return
            }
            val intent = Intent(context, Module_Phone_CallActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
            context.startActivity(intent)
        } else {
            terminateOtherCall()
        }
    }

    /* VFS */

    companion object {
        private const val TRANSFORMATION = "AES/GCM/NoPadding"
        private const val ANDROID_KEY_STORE = "AndroidKeyStore"
        private const val ALIAS = "vfs"
        private const val LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256 = 2
        private const val VFS_IV = "vfsiv"
        private const val VFS_KEY = "vfskey"

        @Throws(java.lang.Exception::class)
        private fun generateSecretKey() {
            val keyGenerator =
                KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
            keyGenerator.init(
                KeyGenParameterSpec.Builder(
                    ALIAS,
                    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                )
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build()
            )
            keyGenerator.generateKey()
        }

        @Throws(java.lang.Exception::class)
        private fun getSecretKey(): SecretKey? {
            val ks = KeyStore.getInstance(ANDROID_KEY_STORE)
            ks.load(null)
            val entry = ks.getEntry(ALIAS, null) as KeyStore.SecretKeyEntry
            return entry.secretKey
        }

        @Throws(java.lang.Exception::class)
        fun generateToken(): String {
            return sha512(UUID.randomUUID().toString())
        }

        @Throws(java.lang.Exception::class)
        private fun encryptData(textToEncrypt: String): Pair<ByteArray, ByteArray> {
            val cipher = Cipher.getInstance(TRANSFORMATION)
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
            val iv = cipher.iv
            return Pair<ByteArray, ByteArray>(
                iv,
                cipher.doFinal(textToEncrypt.toByteArray(StandardCharsets.UTF_8))
            )
        }

        @Throws(java.lang.Exception::class)
        private fun decryptData(encrypted: String?, encryptionIv: ByteArray): String {
            val cipher = Cipher.getInstance(TRANSFORMATION)
            val spec = GCMParameterSpec(128, encryptionIv)
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)
            val encryptedData = Base64.decode(encrypted, Base64.DEFAULT)
            return String(cipher.doFinal(encryptedData), StandardCharsets.UTF_8)
        }

        @Throws(java.lang.Exception::class)
        fun encryptToken(string_to_encrypt: String): Pair<String?, String?> {
            val encryptedData = encryptData(string_to_encrypt)
            return Pair<String?, String?>(
                Base64.encodeToString(encryptedData.first, Base64.DEFAULT),
                Base64.encodeToString(encryptedData.second, Base64.DEFAULT)
            )
        }

        @Throws(java.lang.Exception::class)
        fun sha512(input: String): String {
            val md = MessageDigest.getInstance("SHA-512")
            val messageDigest = md.digest(input.toByteArray())
            val no = BigInteger(1, messageDigest)
            var hashtext = no.toString(16)
            while (hashtext.length < 32) {
                hashtext = "0$hashtext"
            }
            return hashtext
        }

        @Throws(java.lang.Exception::class)
        fun getVfsKey(sharedPreferences: SharedPreferences): String {
            val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
            keyStore.load(null)
            return decryptData(
                sharedPreferences.getString(VFS_KEY, null),
                Base64.decode(sharedPreferences.getString(VFS_IV, null), Base64.DEFAULT)
            )
        }

        fun activateVFS() {
            try {
                Log.i("[Context] Activating VFS")
                val preferences = corePreferences.encryptedSharedPreferences
                if (preferences == null) {
                    Log.e("[Context] Can't get encrypted SharedPreferences, can't init VFS")
                    return
                }

                if (preferences.getString(VFS_IV, null) == null) {
                    generateSecretKey()
                    encryptToken(generateToken()).let { data ->
                        preferences
                            .edit()
                            .putString(VFS_IV, data.first)
                            .putString(VFS_KEY, data.second)
                            .commit()
                    }
                }
                Factory.instance().setVfsEncryption(
                    LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256,
                    getVfsKey(preferences).toByteArray().copyOfRange(0, 32),
                    32
                )

                Log.i("[Context] VFS activated")
            } catch (e: Exception) {
                Log.f("[Context] Unable to activate VFS encryption: $e")
            }
        }
    }

    private fun createIncomingRinoChannel() {
        Log.i("[Context] createIncomingRinoChannel")
        val sipMsgEvent = MessageEvent(
            TYPE_CALL_SEND_CREATE_CHANNEL_EQUEST_INCOMING,
            CallCreateActionBean(
                channelName = ConnectionController.getInstance(context).getRemoteChannelName(),
                aid = agoraId
            )
        )
        EventBus.getDefault().post(sipMsgEvent)
    }

    private fun createIncomingEarlyMediaRinoChannel() {
        Log.i("[Context] createIncomingEarlyMediaRinoChannel")
        val sipMsgEvent = MessageEvent(
            TYPE_CALL_SEND_CREATE_CHANNEL_REQUEST_INCOMING_EARLYMEDIA,
            CallCreateActionBean(
                channelName = ConnectionController.getInstance(context).getRemoteChannelName(),
                aid = agoraId
            )
        )
        EventBus.getDefault().post(sipMsgEvent)
    }

    override val viewModelStore: ViewModelStore
        get() = _viewModelStore
}
